package oc.tm.sg.core.common;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.RandomAccessFile;
import java.nio.channels.FileChannel;
import java.nio.channels.FileLock;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicLong;

import oc.tm.sg.core.util.MD5Utils;
import oc.tm.sg.core.util.StringUtils;

import org.apache.log4j.Logger;

/**
 * @title: 快照中心
 * @description: TODO(用一句话描述该文件做什么)
 * @author lry
 * @date 2016年1月8日 上午10:14:10
 * @version v1.0
 */
public class Snapshot {
	
	// 日志输出
	protected final Logger logger = Logger.getLogger(Snapshot.class);
		
	private File file;
	private String Snapshot_FILE = null;

	// 是否是同步保存文件:默认开启
    private final boolean syncSaveFile=true;
	// 本地磁盘缓存
	private final Properties properties = new Properties();
	private final AtomicLong lastSnapshotChanged = new AtomicLong();
	// 文件快照操作失败重试器
	private ExecutorService snapshotExecutor = null;

	//获取当前工程路径
	public static String path = Snapshot.class.getClassLoader().getResource("").getPath();
	public String PATH_ID=MD5Utils.getStringMD5Simple(path);
	
	/**
	 * @param snapshotName 快照文件名称<br>
	 * eg: System.getProperty("user.home") /.snapshot/MD5(PATH)/<font color="red">snapshotName</font> .snapshot"
	 */
	public Snapshot(String snapshotName) {
		try {
			Snapshot_FILE = System.getProperty("user.home") + "/.snapshot/"+PATH_ID+"/" + snapshotName +".snapshot";
			logger.info("Snapshot store file: " + Snapshot_FILE);
			snapshotExecutor = Executors.newFixedThreadPool(1, new NamedThreadFactory(PATH_ID+"_"+snapshotName+"_SnapshotFailScheduledTimer", true));
			
			File file = null;
			if (!StringUtils.isBlank(Snapshot_FILE)) {
				file = new File(Snapshot_FILE);
				if (!file.exists() && file.getParentFile() != null && !file.getParentFile().exists()) {
					if (!file.getParentFile().mkdirs()) {
						throw new IllegalArgumentException("Invalid snapshot store file " + 
								file + ", cause: Failed to create directory " + file.getParentFile() + "!");
					}
				}
			}

			this.file = file;
			loadSnapshot();
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
	
	public File getSnapshotFile() {
        return file;
    }

    public Properties getSnapshot() {
        return properties;
    }

    public AtomicLong getLastSnapshotChanged(){
        return lastSnapshotChanged;
    }
    
    /**
     * 加载缓存资源
     */
    private void loadSnapshot() {
		if (file != null && file.exists()) {
			InputStream in = null;
			try {
				in = new FileInputStream(file);
				properties.load(in);
				if (logger.isInfoEnabled()) {
					logger.info("Load Snapshot store file " + file);//可能数据太大不宜输出：+",data:"+properties
				}
			} catch (Throwable e) {
				logger.warn("Failed to load Snapshot store file " + file, e);
			} finally {
				if (in != null) {
					try {
						in.close();
					} catch (IOException e) {
						logger.warn(e.getMessage(), e);
					}
				}
			}
		}
	}
    
    /**
     * 保存资源
     * @author lry
     */
	private class SaveProperties implements Runnable {
		private long version;

		private SaveProperties(long version) {
			this.version = version;
		}

		public void run() {
			doSaveProperties(version);
		}
	}
	
	/**
     * 删除资源
     * @author lry
     */
	private class RemoveProperties implements Runnable {
		private long version;
		private List<String> keys;
		private boolean isAll;

		private RemoveProperties(long version,List<String> keys,boolean isAll) {
			this.version = version;
			this.keys=keys;
			this.isAll=isAll;
		}

		public void run() {
			doRemoveProperties(version, keys, isAll);
		}
	}

	/**
	 * 执行保存
	 * @param version
	 */
	private void doSaveProperties(long version) {
		if (version < lastSnapshotChanged.get()) {
			return;
		}
		if (file == null) {
			return;
		}
		Properties newProperties = new Properties();
		// 保存之前先读取一遍，防止多个注册中心之间冲突
		InputStream in = null;
		try {
			if (file.exists()) {
				in = new FileInputStream(file);
				newProperties.load(in);
			}
		} catch (Throwable e) {
			logger.warn("Failed to load snapshot store file, cause: " + e.getMessage(), e);
		} finally {
			if (in != null) {
				try {
					in.close();
				} catch (IOException e) {
					logger.warn(e.getMessage(), e);
				}
			}
		}
		// 保存
		try {
			newProperties.putAll(properties);
			File lockfile = new File(file.getAbsolutePath() + ".lock");
			if (!lockfile.exists()) {
				lockfile.createNewFile();
			}
			RandomAccessFile raf = new RandomAccessFile(lockfile, "rw");
			try {
				FileChannel channel = raf.getChannel();
				try {
					FileLock lock = channel.tryLock();
					if (lock == null) {
						throw new IOException( "Can not lock the Snapshot file " + file.getAbsolutePath()
								+ ", ignore and retry later, maybe multi java process use the file, please config: snapshot.file=xxx.properties");
					}
					// 保存
					try {
						if (!file.exists()) {
							file.createNewFile();
						}
						FileOutputStream outputFile = new FileOutputStream(file);
						try {
							newProperties.store(outputFile, "Snapshot Center");
						} finally {
							outputFile.close();
						}
					} finally {
						lock.release();
					}
				} finally {
					channel.close();
				}
			} finally {
				raf.close();
			}
		} catch (Throwable e) {
			if (version < lastSnapshotChanged.get()) {
				return;
			} else {
				snapshotExecutor.execute(new SaveProperties(lastSnapshotChanged.incrementAndGet()));
			}
			logger.warn("Failed to save Snapshot store file, cause: " + e.getMessage(), e);
		}
	}
	
	/**
	 * 执行保存
	 * @param version
	 */
	private void doRemoveProperties(long version,List<String> keys,boolean isAll) {
		if (version < lastSnapshotChanged.get()) {
			return;
		}
		if (file == null) {
			return;
		}
		Properties newProperties = new Properties();
		// 保存之前先读取一遍，防止多个注册中心之间冲突
		InputStream in = null;
		try {
			if (file.exists()) {
				in = new FileInputStream(file);
				newProperties.load(in);
			}
		} catch (Throwable e) {
			logger.warn("Failed to load snapshot store file, cause: " + e.getMessage(), e);
		} finally {
			if (in != null) {
				try {
					in.close();
				} catch (IOException e) {
					logger.warn(e.getMessage(), e);
				}
			}
		}
		// 保存
		try {
			if(isAll){
				newProperties.clear();
				properties.clear();
			}else{
				for (String key:keys) {
					newProperties.remove(key);
					properties.remove(key);
				}
			}
			File lockfile = new File(file.getAbsolutePath() + ".lock");
			if (!lockfile.exists()) {
				lockfile.createNewFile();
			}
			RandomAccessFile raf = new RandomAccessFile(lockfile, "rw");
			try {
				FileChannel channel = raf.getChannel();
				try {
					FileLock lock = channel.tryLock();
					if (lock == null) {
						throw new IOException( "Can not lock the Snapshot file " + file.getAbsolutePath()
								+ ", ignore and retry later, maybe multi java process use the file, please config: snapshot.file=xxx.properties");
					}
					// 保存
					try {
						if (!file.exists()) {
							file.createNewFile();
						}
						FileOutputStream outputFile = new FileOutputStream(file);
						try {
							newProperties.store(outputFile, "Snapshot Center");
						} finally {
							outputFile.close();
						}
					} finally {
						lock.release();
					}
				} finally {
					channel.close();
				}
			} finally {
				raf.close();
			}
		} catch (Throwable e) {
			if (version < lastSnapshotChanged.get()) {
				return;
			} else {
				snapshotExecutor.execute(new RemoveProperties(version, keys, isAll));
			}
			logger.warn("Failed to remove snapshot store file, cause: " + e.getMessage(), e);
		}
	}
	
	
	/****************对外提供*******************/
	/**
     * 获取当前缓存中的所有推送任务
     * @return
     */
    public Map<String,Object> getSnapshotDatas() {
    	Map<String, Object> map = new LinkedHashMap<String, Object>();
    	for (Map.Entry<Object, Object> entry:getSnapshot().entrySet()) {
    		map.put(entry.getKey()+"",entry.getValue());
		}
        return map;
    }
    
    /**
	 * 保存至缓存
	 * @param data
	 */
	public void save(String key,Object value) {
		Map<String,Object> datas=new LinkedHashMap<String, Object>();
		datas.put(key, value);
		save(datas);
	}
	
	/**
	 * 保存至缓存
	 * @param data
	 */
	public void save(Map<String,Object> datas) {
        if (file == null) {
            return;
        }
        try {
        	for (Map.Entry<String,Object> data:datas.entrySet()) {
        		 properties.setProperty(data.getKey(),data.getValue()+"");
			}
            long version = lastSnapshotChanged.incrementAndGet();
            if (syncSaveFile) {
            	doSaveProperties(version);
            }else {
                snapshotExecutor.execute(new SaveProperties(version));
            }
        } catch (Throwable t) {
            logger.warn(t.getMessage(), t);
        }
    }
	
	/**
	 * 移除缓存文件中的多条记录
	 * @param data
	 */
	public void remove(List<String> keys) {
        if (file == null) {
            return;
        }
        try {
            long version = lastSnapshotChanged.incrementAndGet();
            if (syncSaveFile) {
            	doRemoveProperties(version,keys,false);
            }else {
                snapshotExecutor.execute(new RemoveProperties(version, keys, false));
            }
        } catch (Throwable t) {
            logger.warn(t.getMessage(), t);
        }
    }
	
	/**
	 * 移除缓存文件中的一条记录
	 * @param data
	 */
	public void remove(String key) {
		List<String> keys=new ArrayList<String>();
		keys.add(key);
		remove(keys);
    }
	
	/**
	 * 清空缓存文件中的所有记录
	 * @param data
	 */
	public void clear() {
		if (file == null) {
            return;
        }
        try {
            long version = lastSnapshotChanged.incrementAndGet();
            if (syncSaveFile) {
            	doRemoveProperties(version,null,true);
            }else {
                snapshotExecutor.execute(new RemoveProperties(version, null, true));
            }
        } catch (Throwable t) {
            logger.warn(t.getMessage(), t);
        }
    }
}
